﻿namespace System.Linq
{
	using System.Collections.Generic;

	/// <summary>
	/// Provides a set of extension methods for enumerables.
	/// </summary>
	public static class EnumerableExtensions
	{
		#region Mapping
		/// <summary>
		/// Projects each element of a sequence into a new form.
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <typeparam name="TResult"></typeparam>
		/// <param name="source"></param>
		/// <param name="selector"></param>
		/// <returns></returns>
		/// <remarks>This method is a synonym for Select.</remarks>
		public static IEnumerable<TResult> Map<T, TResult>(this IEnumerable<T> source, Func<T, TResult> selector)
		{
			return source.Select(selector);
		}

		/// <summary>
		/// Replaces every occurrences of subject to replacement.
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="source"></param>
		/// <param name="subject">The element to be replaced.</param>
		/// <param name="replacement">The element to replace all occurrences of subject.</param>
		/// <returns></returns>
		public static IEnumerable<T> Replace<T>(this IEnumerable<T> source, T subject, T replacement) where T : IEquatable<T>
		{
			if (source == null)
				throw new ArgumentNullException("source");

			return source.Replace(element => element.Equals(subject), replacement);
		}
		/// <summary>
		/// Replaces every element that matches a given predicate to another element.
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="source"></param>
		/// <param name="predicate">The predicate to match elements to be replaced.</param>
		/// <param name="replacement">The element to replace all elements that match predicate.</param>
		/// <returns></returns>
		public static IEnumerable<T> Replace<T>(this IEnumerable<T> source, Func<T, bool> predicate, T replacement)
		{
			if (source == null)
				throw new ArgumentNullException("source");

			if (predicate == null)
				throw new ArgumentNullException("predicate");

			return source.Select(element => predicate(element) ? replacement : element);
		}
		/// <summary>
		/// Replaces elements based on a map.
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="source"></param>
		/// <param name="map">A dictionary that contains the mappings.</param>
		/// <returns></returns>
		public static IEnumerable<T> Replace<T>(this IEnumerable<T> source, IDictionary<T, T> map)
		{
			if (source == null)
				throw new ArgumentNullException("source");

			if (map == null)
				throw new ArgumentNullException("map");

			return source.Select(element => map.ContainsKey(element) ? map[element] : element);
		}

		/// <summary>
		/// Applies a specified transform on the sequence.
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <typeparam name="TResult"></typeparam>
		/// <param name="source"></param>
		/// <param name="transform">The transform to apply.</param>
		/// <returns>Returns the transformed sequence.</returns>
		public static IEnumerable<TResult> Apply<T, TResult>(this IEnumerable<T> source, Func<IEnumerable<T>, IEnumerable<TResult>> transform)
		{
			if (source == null)
				throw new ArgumentNullException("source");

			if (transform == null)
				throw new ArgumentNullException("transform");

			return transform(source);
		}
		#endregion

		#region Search
		/// <summary>
		/// Reports the zero-based index of the first occurrence of the specified element.
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="source"></param>
		/// <param name="element">An element to seek.</param>
		/// <param name="startIndex">The search starting position.</param>
		/// <returns></returns>
		public static int IndexOf<T>(this IEnumerable<T> source, T element, int startIndex = 0) where T : IEquatable<T>
		{
			if (source == null)
				throw new ArgumentNullException("source");

			if (startIndex < 0)
				throw new ArgumentOutOfRangeException("startIndex");

			int currentIndex = 0;

			foreach (T item in source)
			{
				if (currentIndex < startIndex)
				{
					currentIndex++;
					continue;
				}

				if (item.Equals(element))
					return currentIndex;

				currentIndex++;
			}
			
			return -1;
		}
		/// <summary>
		/// Reports the zero-based index of the first occurrence of any of specified elements.
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="source"></param>
		/// <param name="anyOf">Elements to seek.</param>
		/// <param name="startIndex">The search starting position.</param>
		/// <returns></returns>
		public static int IndexOfAny<T>(this IEnumerable<T> source, IEnumerable<T> anyOf, int startIndex = 0)
		{
			if (source == null)
				throw new ArgumentNullException("source");

			if (anyOf == null)
				throw new ArgumentNullException("anyOf");

			int currentIndex = 0;

			foreach (T item in source)
			{
				if (currentIndex < startIndex)
				{
					currentIndex++;
					continue;
				}

				if (anyOf.Contains(item))
					return currentIndex;

				currentIndex++;
			}
			
			return -1;
		}

		/// <summary>
		/// Returns the element that yields the maximum value using a selector function.
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <typeparam name="TSelector"></typeparam>
		/// <param name="source"></param>
		/// <param name="selector">Selects a value from an element, the comparison will be based on this value.</param>
		/// <returns></returns>
		public static T ElementAtMax<T, TSelector>(this IEnumerable<T> source, Func<T, TSelector> selector) where TSelector : IComparable<TSelector>
		{
			if (source == null)
				throw new ArgumentNullException("source");

			if (selector == null)
				throw new ArgumentNullException("selector");

			T maxElement;
			TSelector maxValue;

			using (IEnumerator<T> enumerator = source.GetEnumerator())
			{
				if (!enumerator.MoveNext())
					throw new InvalidOperationException("Sequence contains no elements.");

				maxElement = enumerator.Current;
				maxValue = selector(enumerator.Current);

				while (enumerator.MoveNext())
				{
					TSelector value = selector(enumerator.Current);

					if (value.CompareTo(maxValue) > 0)
					{
						maxValue = value;
						maxElement = enumerator.Current;
					}
				}
			}

			return maxElement;
		}

		/// <summary>
		/// Returns the element that yields the minimum value using a selector function.
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <typeparam name="TSelector"></typeparam>
		/// <param name="source"></param>
		/// <param name="selector">Selects a value from an element, the comparison will be based on this value.</param>
		/// <returns></returns>
		public static T ElementAtMin<T, TSelector>(this IEnumerable<T> source, Func<T, TSelector> selector) where TSelector : IComparable<TSelector>
		{
			if (source == null)
				throw new ArgumentNullException("source");

			if (selector == null)
				throw new ArgumentNullException("selector");

			T minElement;
			TSelector minValue;

			using (IEnumerator<T> enumerator = source.GetEnumerator())
			{
				if (!enumerator.MoveNext())
					throw new InvalidOperationException("Sequence contains no elements.");

				minElement = enumerator.Current;
				minValue = selector(enumerator.Current);

				while (enumerator.MoveNext())
				{
					TSelector value = selector(enumerator.Current);

					if (value.CompareTo(minValue) < 0)
					{
						minValue = value;
						minElement = enumerator.Current;
					}
				}
			}

			return minElement;
		}
		#endregion

		#region Subsequences
		/// <summary>
		/// Retrieves a subsequence from a sequence. The subsequence starts at a specified position and has a specified length.
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="source"></param>
		/// <param name="startIndex">The zero-based starting element position of a subsequence in this sequence.</param>
		/// <param name="count">The number of elements in the subsequence.</param>
		/// <returns></returns>
		public static IEnumerable<T> Subsequence<T>(this IEnumerable<T> source, int startIndex, int count)
		{
			if (source == null)
				throw new ArgumentNullException("source");

			if (startIndex < 0)
				throw new ArgumentOutOfRangeException("startIndex");

			if (count < 0)
				throw new ArgumentOutOfRangeException("count");

			if (count == 0)
				return Enumerable.Empty<T>();

			return source.Skip(startIndex).Take(count);
		}

		/// <summary>
		/// Bypasses a specified number of elements from a given position and returns the remaining elements.
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="source"></param>
		/// <param name="startIndex">The zero-based starting element position where skipping starts.</param>
		/// <param name="count">The number of elements to skip.</param>
		/// <returns></returns>
		public static IEnumerable<T> Skip<T>(this IEnumerable<T> source, int startIndex, int count)
		{
			if (source == null)
				throw new ArgumentNullException("source");

			if (startIndex < 0)
				throw new ArgumentOutOfRangeException("startIndex");

			if (count < 0)
				throw new ArgumentOutOfRangeException("count");

			if (count == 0)
				return Enumerable.Empty<T>();

			return source.Take(startIndex).Concat(source.Skip(startIndex + count));
		}

		/// <summary>
		/// Takes every nth element of the source.
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="source"></param>
		/// <param name="n"></param>
		/// <returns></returns>
		public static IEnumerable<T> TakeEvery<T>(this IEnumerable<T> source, int n)
		{
			if (source == null)
				throw new ArgumentNullException("source");

			if (n <= 0)
				throw new ArgumentOutOfRangeException("n");

			if (n == 1)
				return source;

			return source.Where((element, index) => (index + 1) % n == 0);
		}

		/// <summary>
		/// Skips every nth element of the source.
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="source"></param>
		/// <param name="n"></param>
		/// <returns></returns>
		public static IEnumerable<T> SkipEvery<T>(this IEnumerable<T> source, int n)
		{
			if (source == null)
				throw new ArgumentNullException("source");

			if (n <= 0)
				throw new ArgumentOutOfRangeException("n");

			if (n == 1)
				return Enumerable.Empty<T>();

			return source.Where((element, index) => (index + 1) % n != 0);
		}

		/// <summary>
		/// Splits a sequence into subsequences with a specified length.
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="source"></param>
		/// <param name="size">Number of elements in a subsequence.</param>
		/// <returns></returns>
		public static IEnumerable<IEnumerable<T>> Split<T>(this IEnumerable<T> source, int size)
		{
			if (source == null)
				throw new ArgumentNullException("source");

			if (size <= 0)
				throw new ArgumentOutOfRangeException("size");

			ICollection<T> part = new List<T>(size);

			foreach (T element in source)
			{
				part.Add(element);

				if (part.Count == size)
				{
					yield return part;
					part = new List<T>(size);
				}
			}

			if (part.Any())
				yield return part;
		}
		/// <summary>
		/// Splits a sequence into subsequences by a specified separator.
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="source"></param>
		/// <param name="separator">The separator element.</param>
		/// <returns></returns>
		public static IEnumerable<IEnumerable<T>> Split<T>(this IEnumerable<T> source, T separator) where T : IEquatable<T>
		{
			if (source == null)
				throw new ArgumentNullException("source");

			ICollection<T> part = new List<T>();

			foreach (T element in source)
			{
				if (element.Equals(separator))
				{
					yield return part;
					part = new List<T>();
				}
				else
					part.Add(element);
			}

			if (part.Any())
				yield return part;
		}
		/// <summary>
		/// Splits a sequence into subsequences by specified separators.
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="source"></param>
		/// <param name="separators">The separator elements.</param>
		/// <returns></returns>
		public static IEnumerable<IEnumerable<T>> Split<T>(this IEnumerable<T> source, IEnumerable<T> separators) where T : IEquatable<T>
		{
			if (source == null)
				throw new ArgumentNullException("source");

			if (separators == null)
				throw new ArgumentNullException("separators");

			ICollection<T> buffer = new List<T>();

			foreach (T element in source)
			{
				if (separators.Contains(element))
				{
					yield return buffer;
					buffer = new List<T>();
				}
				else
					buffer.Add(element);
			}

			if (buffer.Any())
				yield return buffer;
		}

        /// <summary>
        /// Prepends an element to the beginning of a sequence.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="source"></param>
        /// <param name="first">The element to be used to prepend the original sequence.</param>
        /// <returns></returns>
        public static IEnumerable<T> Prepend<T>(this IEnumerable<T> source, T first)
        {
            if (source == null)
                throw new ArgumentNullException("source");

            yield return first;
            
            foreach (T element in source)
                yield return element;
        }

        /// <summary>
        /// Concatenates two sequences.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="source"></param>
        /// <param name="second">The sequence to prepend the first sequence.</param>
        /// <returns></returns>
        public static IEnumerable<T> Prepend<T>(this IEnumerable<T> source, IEnumerable<T> second)
        {
            if (source == null)
                throw new ArgumentNullException("source");

            if (second == null)
                throw new ArgumentNullException("second");

            return second.Concat(source);
        }

        /// <summary>
        /// Appends an element to the end of a sequence.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="source"></param>
        /// <param name="last">The element to append to the end of the original sequence.</param>
        /// <returns></returns>
        public static IEnumerable<T> Append<T>(this IEnumerable<T> source, T last)
        {
            if (source == null)
                throw new ArgumentNullException("source");

            foreach (T element in source)
                yield return element;

            yield return last;
        }

        /// <summary>
        /// Concatenates two sequences.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="source"></param>
        /// <param name="second">The sequence to concatenate to the first sequence.</param>
        /// <returns></returns>
        public static IEnumerable<T> Append<T>(this IEnumerable<T> source, IEnumerable<T> second)
        {
            if (source == null)
                throw new ArgumentNullException("source");

            if (second == null)
                throw new ArgumentNullException("second");

            return source.Concat(second);
        }
		#endregion

		#region Sorting
		/// <summary>
		/// Cycles the elements of a sequence forward by a given offset.
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="source"></param>
		/// <param name="offset">The offset to shift by.</param>
		/// <returns></returns>
		public static IEnumerable<T> CycleForward<T>(this IEnumerable<T> source, int offset = 1)
		{
			if (source == null)
				throw new ArgumentNullException("source");

			if (offset < 0)
				throw new ArgumentOutOfRangeException("offset");

			if (offset == 0)
				return source;

			int elementCount = source.Count();

			if (elementCount == 0)
				return Enumerable.Empty<T>();

			int normalized = offset % elementCount;

			return source.Skip(elementCount - normalized).Concat(source.Take(elementCount - normalized));
		}

		/// <summary>
		/// Cycles the elements of a sequence backward by a given offset.
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="source"></param>
		/// <param name="offset">The offset to shift by.</param>
		/// <returns></returns>
		public static IEnumerable<T> CycleBackward<T>(this IEnumerable<T> source, int offset = 1)
		{
			if (source == null)
				throw new ArgumentNullException("source");

			if (offset < 0)
				throw new ArgumentOutOfRangeException("offset");

			if (offset == 0)
				return source;

			int elementCount = source.Count();

			if (elementCount == 0)
				return Enumerable.Empty<T>();

			int normalized = offset % elementCount;

			return source.Skip(normalized).Concat(source.Take(normalized));
		}

		/// <summary>
		/// Returns elements in a random order.
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="source"></param>
		/// <returns>Returns elements in a random order.</returns>
		public static IEnumerable<T> Shuffle<T>(this IEnumerable<T> source)
		{
			if (source == null)
				throw new ArgumentNullException("source");

			Random random = new Random();

			return source.OrderBy(e => random.Next());
		}
		#endregion

		#region Set operations
		/// <summary>
		/// Filters elements based on a predicate.
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="source"></param>
		/// <param name="predicate">Elements match this predicate will be excluded.</param>
		/// <returns></returns>
		public static IEnumerable<T> Except<T>(this IEnumerable<T> source, Func<T, bool> predicate)
		{
			if (source == null)
				throw new ArgumentNullException("source");

			if (predicate == null)
				throw new ArgumentNullException("predicate");

			return source.Where(element => !predicate(element));
		}

		/// <summary>
		/// Determines whether the specified set is a subset of this set.
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="source"></param>
		/// <param name="second">The set to compare.</param>
		/// <returns></returns>
		public static bool IsSubsetOf<T>(this IEnumerable<T> source, IEnumerable<T> second, bool strict = false)
		{
			if (source == null)
				throw new ArgumentNullException("source");

			if (second == null)
				throw new ArgumentNullException("second");

			bool isSubSet = source.All(element => second.Contains(element));

			if (strict)
				return isSubSet && !second.All(element => source.Contains(element));

			return isSubSet;
		}

		/// <summary>
		/// Determines whether the specified set is a superset of this set.
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="source"></param>
		/// <param name="second">The set to compare.</param>
		/// <returns></returns>
		public static bool IsSupersetOf<T>(this IEnumerable<T> source, IEnumerable<T> second, bool strict = false)
		{
			if (source == null)
				throw new ArgumentNullException("source");

			if (second == null)
				throw new ArgumentNullException("second");

			bool isSuperSet = second.All(element => source.Contains(element));

			if (strict)
				return isSuperSet && !source.All(element => second.Contains(element));

			return isSuperSet;
		}

		/// <summary>
		/// Determines whether the two sets contain the same elements.
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="source"></param>
		/// <param name="second">The set to compare.</param>
		/// <returns></returns>
		public static bool SetEqual<T>(this IEnumerable<T> source, IEnumerable<T> second)
		{
			if (source == null)
				throw new ArgumentNullException("source");

			if (second == null)
				throw new ArgumentNullException("second");

			return source.IsSubsetOf(second) && source.IsSupersetOf(second);
		}
		#endregion

		#region Folding
		/// <summary>
		/// Applies an accumulator function over a sequence.
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="source"></param>
		/// <param name="accumulator">An accumulator function to be invoked on each element.</param>
		/// <returns></returns>
		/// <remarks>This method is a synonym for Aggregate.</remarks>
		public static T Fold<T>(this IEnumerable<T> source, Func<T, T, T> accumulator)
		{
			return source.Fold(default(T), accumulator);
		}
		/// <summary>
		/// Applies an accumulator function over a sequence. The specified seed value is used as the initial accumulator value.
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <typeparam name="TResult"></typeparam>
		/// <param name="source"></param>
		/// <param name="seed">The initial accumulator value.</param>
		/// <param name="accumulator">An accumulator function to be invoked on each element.</param>
		/// <returns></returns>
		/// <remarks>This method is a synonym for Aggregate.</remarks>
		public static TResult Fold<T, TResult>(this IEnumerable<T> source, TResult seed, Func<TResult, T, TResult> accumulator)
		{
			return source.Aggregate(seed, accumulator);
		}
		/// <summary>
		/// Folds all the elements into a single element.
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <typeparam name="TResult"></typeparam>
		/// <param name="source"></param>
		/// <param name="accumulator">An accumulator function that merges all elements into a single one.</param>
		/// <returns>Returns the result of folding.</returns>
		public static TResult Fold<T, TResult>(this IEnumerable<T> source, Func<IEnumerable<T>, TResult> accumulator)
		{
			if (source == null)
				throw new ArgumentNullException("source");

			if (accumulator == null)
				throw new ArgumentNullException("accumulator");

			return accumulator(source);
		}

		/// <summary>
		/// Folds every consecutive two elements to a new one.
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <typeparam name="TResult"></typeparam>
		/// <param name="source"></param>
		/// <param name="accumulator">An accumulator function to be invoked on each pair.</param>
		/// <returns></returns>
		public static IEnumerable<TResult> Differentiate<T, TResult>(this IEnumerable<T> source, Func<T, T, TResult> accumulator)
		{
			if (source == null)
				throw new ArgumentNullException("source");
			
			using (IEnumerator<T> enumerator = source.GetEnumerator())
			{
				if (!enumerator.MoveNext())
					yield break;

				T last = enumerator.Current;

				while (enumerator.MoveNext())
				{
					yield return accumulator(last, enumerator.Current);

					last = enumerator.Current;
				}
			}
		}
		/// <summary>
		/// Makes pairs of consecutive elements and folds every pair into a new element.
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="source"></param>
		/// <param name="accumulator">Folds a pair of elements into a new element.</param>
		/// <returns>Returns the result of folding.</returns>
		public static IEnumerable<T> BinaryFold<T>(this IEnumerable<T> source, Func<T, T, T> accumulator)
		{
			if (source == null)
				throw new ArgumentNullException("source");

			using (IEnumerator<T> enumerator = source.GetEnumerator())
			{
				while (enumerator.MoveNext())
				{
					T left = enumerator.Current;

					if (!enumerator.MoveNext())
						throw new InvalidOperationException("Sequence must contain an even number of elements.");

					T right = enumerator.Current;

					yield return accumulator(left, right);
				}
			}
		}
		/// <summary>
		/// Makes pairs of consecutive elements and folds every pair into a new element recursively until only one single element remains.
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="source"></param>
		/// <param name="accumulator">Folds a pair of elements into a new element.</param>
		/// <returns>Returns the result of folding.</returns>
		public static T BinaryFoldRecursive<T>(this IEnumerable<T> source, Func<T, T, T> accumulator)
		{
			if (source == null)
				throw new ArgumentNullException("source");

			if (accumulator == null)
				throw new ArgumentNullException("accumulator");

			IList<T> temporary = new List<T>();

			// fold the first level
			using (IEnumerator<T> enumerator = source.GetEnumerator())
			{
				while (enumerator.MoveNext())
				{
					T left = enumerator.Current;

					if (!enumerator.MoveNext())
						throw new InvalidOperationException("The count of elements in the sequence must be a power of two.");

					T right = enumerator.Current;

					temporary.Add(accumulator(left, right));
				}				
			}

			// check count of elements
			if (!temporary.Any())
				throw new InvalidOperationException("Sequence contains no elements.");

			if ((temporary.Count & (temporary.Count - 1)) != 0) // decrement and compare method
				throw new InvalidOperationException("The count of elements in the sequence must be a power of two.");

			// fold recursively
			while (temporary.Count != 1)
			{
				IList<T> newLevel = new List<T>(temporary.Count / 2);

				for (int i = 0; i < temporary.Count - 1; i += 2)
					newLevel.Add(accumulator(temporary[i], temporary[i + 1]));

				temporary = newLevel;
			}

			return temporary.Single();
		}

		/// <summary>
		/// Unfolds this sequence to another sequence using a generator function.
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="source"></param>
		/// <param name="generator">The generator function that generated a new element based on the current sequence so far.</param>
		/// <returns></returns>
		public static IEnumerable<T> Unfold<T>(this IList<T> source, Func<IList<T>, T> generator)
		{
			if (source == null)
				throw new ArgumentNullException("source");

			if (generator == null)
				throw new ArgumentNullException("generator");

			IList<T> previousItems = new List<T>(source);

			foreach (T item in source)
				yield return item;

			while (true)
			{
				T newItem = generator(previousItems);
				previousItems.Add(newItem);
				yield return newItem;
			}
		}

		/// <summary>
		/// Executes a map and a reduction function on the sequence.
		/// </summary>
		/// <typeparam name="S"></typeparam>
		/// <typeparam name="M"></typeparam>
		/// <typeparam name="K"></typeparam>
		/// <typeparam name="R"></typeparam>
		/// <param name="source"></param>
		/// <param name="mapper">The mapper function that expands the elements.</param>
		/// <param name="keySelector">The key selector function for grouping.</param>
		/// <param name="reducer">The reducer function to reduce groups.</param>
		/// <returns></returns>
		public static IEnumerable<R> MapReduce<S, M, K, R>(this IEnumerable<S> source,
			Func<S, IEnumerable<M>> mapper,
			Func<M, K> keySelector,
			Func<K, IEnumerable<M>, R> reducer
		)
		{
			if (source == null)
				throw new ArgumentNullException("source");

			if (mapper == null)
				throw new ArgumentNullException("mapper");

			if (keySelector == null)
				throw new ArgumentNullException("keySelector");

			if (reducer == null)
				throw new ArgumentNullException("reducer");

			return source.SelectMany(mapper).GroupBy(keySelector, reducer);
		}
		#endregion

		#region Actions
		/// <summary>
		/// Executes a given action on every element of an enumerable.
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="source"></param>
		/// <param name="action">An action to be invoked on each element.</param>
		public static void ForEach<T>(this IEnumerable<T> source, Action<T> action)
		{
			if (source == null)
				throw new ArgumentNullException("source");

			if (action == null)
				throw new ArgumentNullException("action");

			foreach (T element in source)
				action(element);
		}
		/// <summary>
		/// Executes the corresponding action on every element.
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="source"></param>
		/// <param name="actions">The sequence of actions.</param>
		public static void ForEach<T>(this IEnumerable<T> source, IEnumerable<Action<T>> actions)
		{
			if (source == null)
				throw new ArgumentNullException("source");

			if (actions == null)
				throw new ArgumentNullException("actions");

			using (IEnumerator<T> sourceEnumerator = source.GetEnumerator())
			using (IEnumerator<Action<T>> actionsEnumerator = actions.GetEnumerator())
			{
				while (sourceEnumerator.MoveNext())
				{
					if (!actionsEnumerator.MoveNext())
						throw new ArgumentOutOfRangeException("actions");

					if (actionsEnumerator.Current == null)
						throw new InvalidOperationException("Actions cannot contain null values.");

					actionsEnumerator.Current(sourceEnumerator.Current);
				}
			}
		}
		#endregion

		#region Multiple sequences
		/// <summary>
		/// Determines whether the start of the sequence matches the specified sequence using the specified equality comparer.
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="source"></param>
		/// <param name="second">The sequence to compare.</param>
		/// <param name="comparer">The equality comparer used to compare the elements.</param>
		/// <returns></returns>
		public static bool StartsWith<T>(this IEnumerable<T> source, IEnumerable<T> second, IEqualityComparer<T> comparer)
		{
			if (source == null)
				throw new ArgumentNullException("source");

			if (second == null)
				throw new ArgumentNullException("second");

			if (comparer == null)
				throw new ArgumentNullException("comparer");

			using (IEnumerator<T> secondEnumerator = second.GetEnumerator())
			using (IEnumerator<T> sourceEnumerator = source.GetEnumerator())
			{
				while (secondEnumerator.MoveNext())
				{
					if (!sourceEnumerator.MoveNext())
						return false;

					if (!comparer.Equals(secondEnumerator.Current, sourceEnumerator.Current))
						return false;
				}
			}

			return true;
		}
		/// <summary>
		/// Determines whether the start of the sequence matches the specified sequence.
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="source"></param>
		/// <param name="second">The sequence to match.</param>
		/// <returns></returns>
		public static bool StartsWith<T>(this IEnumerable<T> source, IEnumerable<T> second)
		{
			return source.StartsWith(second, EqualityComparer<T>.Default);
		}

		/// <summary>
		/// Determines whether the end of the sequence matches the specified sequence using the specified equality comparer.
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="source"></param>
		/// <param name="second">The sequence to compare.</param>
		/// <param name="comparer">The equality comparer used to compare the elements.</param>
		/// <returns></returns>
		public static bool EndsWith<T>(this IEnumerable<T> source, IEnumerable<T> second, IEqualityComparer<T> comparer)
		{
			if (source == null)
				throw new ArgumentNullException("source");

			if (second == null)
				throw new ArgumentNullException("second");

			if (comparer == null)
				throw new ArgumentNullException("comparer");

			return source.Reverse().StartsWith(second.Reverse(), comparer);
		}
		/// <summary>
		/// Determines whether the end of the sequence matches the specified sequence.
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="source"></param>
		/// <param name="second">The sequence to match.</param>
		/// <returns></returns>
		public static bool EndsWith<T>(this IEnumerable<T> source, IEnumerable<T> second)
		{
			return source.EndsWith(second, EqualityComparer<T>.Default);
		}

		/// <summary>
		/// Merges two sequences by using the specified predicate function.
		/// </summary>
		/// <typeparam name="T1"></typeparam>
		/// <typeparam name="T2"></typeparam>
		/// <typeparam name="TResult"></typeparam>
		/// <param name="source"></param>
		/// <param name="second">The second sequence to merge.</param>
		/// <param name="resultSelector">A function that specified how to merge the elements from the two sequences.</param>
		/// <returns></returns>
		/// <remarks>This method is a synonym for Zip.</remarks>
		public static IEnumerable<TResult> Combine<T1, T2, TResult>(this IEnumerable<T1> source, IEnumerable<T2> second, Func<T1, T2, TResult> resultSelector)
		{
			if (source == null)
				throw new ArgumentNullException("source");

			if (second == null)
				throw new ArgumentNullException("second");

			if (resultSelector == null)
				throw new ArgumentNullException("resultSelector");

			using (IEnumerator<T1> sourceEnumerator = source.GetEnumerator())
			using (IEnumerator<T2> secondEnumerator = second.GetEnumerator())
			{
				while (sourceEnumerator.MoveNext() && secondEnumerator.MoveNext())
					yield return resultSelector(sourceEnumerator.Current, secondEnumerator.Current);
			}
		}
		#endregion

		#region Index based operations
		/// <summary>
		/// Excludes elements at specified indexes.
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="source"></param>
		/// <param name="indexes">The indexes of elements to exclude.</param>
		/// <returns></returns>
		public static IEnumerable<T> Except<T>(this IEnumerable<T> source, IEnumerable<int> indexes)
		{
			if (source == null)
				throw new ArgumentNullException("source");

			if (indexes == null)
				throw new ArgumentNullException("indexes");

			return source.Where((element, index) => !indexes.Contains(index));
		}

		/// <summary>
		/// Takes elements at specified indexes.
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="source"></param>
		/// <param name="indexes">The indexes of elements to take.</param>
		/// <returns></returns>
		public static IEnumerable<T> Take<T>(this IEnumerable<T> source, IEnumerable<int> indexes)
		{
			if (source == null)
				throw new ArgumentNullException("source");

			if (indexes == null)
				throw new ArgumentNullException("indexes");

			return source.Where((element, index) => indexes.Contains(index));
		}
		#endregion

		#region Miscellaneous
		/// <summary>
		/// Adds a specified separator between each element.
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="source"></param>
		/// <param name="separator">The separator element to add between the elements.</param>
		/// <returns></returns>
		public static IEnumerable<T> Separate<T>(this IEnumerable<T> source, T separator)
		{
			if (source == null)
				throw new ArgumentNullException("source");

			bool isFirst = true;

			foreach (T element in source)
			{
				if (!isFirst)
					yield return separator;

				yield return element;

				isFirst = false;
			}
		}
		#endregion
	}
}
